Nakama Authoritative Multiplayer
#Nakama
Autoritative = 権威的な、集権的な
Realtime vs Authoritative
Nakama Realtime Multiplayer
Nakamaは単純な relayed multiplayerをサポートする。
こちらは単にメッセージをリレーするだけなので、マルチプレイ全体をサポートするには
host - client 方式になる。
ただし、リレーするだけなので検査無しで他のクライアントにメッセージが送られる
なので、曖昧or悪意あるメッセージがクライアントから送られることもある(つまりチートとか)
プレイヤー数が少ない場合はRealtime multiplayerは有効だが、
もっとたくさんのプレイヤー、もしくは中央集権的なstate管理が必要な場合は微妙
Realtime
検査なし
サーバーは stateless
サーバーは message をリレーするだけ
クライアント側でホスト-クライアント方式
Authoritative
サーバーサイドでコードを実行
サーバーに state を持つ
固定 tick rate でカスタムマッチロジックを実行
メッセージを検証
state が変更されたら 接続 peer に broadcast
できること
Asynchronous real-time authoritative
ペースの早いリアルタイムプレイ。高い tick rate が必要
Active turn-based
2人以上のプレーヤーが接続され、ターンベースのクイックマッチ
入力に即座に応答することが求められる。ただし real-time よりティックレートは少なくていい
Clash Royale, Stormbound
Passive turn-based
1プレイが数週間~
Words with Friends
Concepts
Match Handler
カスタムマッチロジックを実装するための6つの関数
Tick rate
入力がなくても、定期的にmatch_loop関数を呼び出す
ゲームエンジン側の _process, _physics_processのようなもの
一定時間入力のないプレイヤーをキックできたりする。
一般的に、Turn base なら 1 回/sec, 早いリアルタイムなら 10~ 回/sec
Match State
RAMメモリ領域に保存される、match に関する情報
match_loopやその他 Match Handlerで変更される
Host Node
メモリ内の match state を維持、match_loopを実行するための CPU使用量割当(リソース)を担当する
メッセージは Host Nodeにバッファリングされ、次のmatch_loopで使える
match のある場所(Node)は複製されるので、Nakama Server がクラスタリングされていてもアクセスできる
Minimum Match handler
hookのリスト
go でもかける
code: .lua
local M = {}
function M.match_init(context, setupstate)
local gamestate = {}
local tickrate = 10
local label = ""
return gamestate, tickrate, label
end
function M.match_join_attempt(context, dispatcher, tick, state, presence, metadata)
local acceptuser = true
return state, acceptuser
end
function M.match_join(context, dispatcher, tick, state, presences)
return state
end
function M.match_leave(context, dispatcher, tick, state, presences)
return state
end
function M.match_loop(context, dispatcher, tick, state, messages)
return state
end
function M.match_terminate(context, dispatcher, tick, state, grace_seconds)
return state
end
return M
Create authoritative matches
2つの方法
手動(RPC)
nk.match_create関数を使う
match_module名
initial_state
戻り値はmatch_id
これを実行する rpcを登録して、クライアント側から call_rpcメソッドを使って
作成させる。
code: .lua
local nk = require("nakama")
local function create_match(context, payload)
local modulename = "pingpong"
local setupstate = { initialstate = payload }
local matchid = nk.match_create(modulename, setupstate)
-- Send notification of some kind
return matchid
end
nk.register_rpc(create_match, "create_match_rpc")
Matchmaker
Matchmaker は、完了したらmatch_tokenを返す。
実は、これはサーバーサイドで作成した matchのmatch_id
この方法で作成したマッチを、relayではなくcustom matchにするには
match 作成関数を
nk.register_matchmaker_matched(func)でコールバックを登録する
code: .lua
local nk = require("nakama")
local function makematch(context, matched_users)
local modulename = "pingpong"
local setupstate = { invited = matched_users }
local matchid = nk.match_create(modulename, setupstate)
return matchid
end
nk.register_matchmaker_matched(makematch)
makematch関数の戻り値が match_idならカスタムマッチ
nilなら relay match になる
End Authoritative multiplayer
Authoritative match はプレイヤーが全員離脱しても自動的に終了しない。
ゲーム世界が進行している間、ユーザーが一時切断できるユースケースをサポートしている。
いずれかのコールバックが nilを返すと終了する(プレーヤーが残っていても)
もしくはエラーが発生しても即座に終了する。
List match
アクティブなマッチを一覧表示する。
M.match_init()で返したlabelの文字列で検索する。
code: .lua
local nk = require("nakama")
local limit = 10
local isAuthoritative = true
local label = "skill=100-150"
local min_size = 0
local max_size = 4
local matches = nk.match_list(limit, isAuthoritative, label, min_size, max_size)
for _, match in ipairs(matches) do
nk.logger_info(("Match id %s"):format(match.match_id))
end
nk.match_list関数をサーバーサイドで実行して
マッチ作成関数が呼び出されたとき、既に存在しているマッチから選んで返すパターン
code: .lua
local nk = require("nakama")
local function findorcreatematch(limit, label, min_size, max_size)
local matches = nk.match_list(limit, true, label, min_size, max_size)
if (#matches > 0) then
table.sort(matches, function(a, b)
return a.size > b.size;
end)
return matches1.match_id
end
local modulename = "supermatch"
local initialstate = {}
local match_id = nk.match_create(modulename, initialstate)
return match_id
end
Search query
code: .lua
local query = "+label.mode:freeforall label.level:>10"
local matches = nk.match_list(limit, isauthoritative, label, min_size, max_size, query)
Bleve構文を使ったクエリで、ラベルを検索できる。
Runtime Code
Go と Lua がある。
どちらを使ってもかける。
基本的には Nakama module API を使いつつ
6つの match handler を実装するだけ
https://heroiclabs.com/docs/gameplay-multiplayer-server-multiplayer/